package com.jacoco.incremental.utils.xml;

import com.jacoco.incremental.config.JacocoConfig;
import com.jacoco.incremental.utils.Empty;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.QName;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;

import java.io.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ConfigXml {
    private Map<String, String> config;
    //    包含：存在include 就只显示其内容，不在include中的不展示
    private List<String> include;
    //    排除
    private List<String> exclude;

    private JacocoConfig jacocoConfig;

    /**
     * @param xmlPath build.xml路径
     */
    private void jacocoXml(String xmlPath) throws IOException {
        File file = new File(xmlPath);
        if (!file.exists()) {
            File parentFile = file.getParentFile();
            if (!parentFile.exists()) parentFile.mkdirs();
        } else {
            if (!file.isFile()) file.delete();
        }
        Document doc = DocumentHelper.createDocument();
        Element project = setProject(doc);

        setProperties(project, config);
        setTaskDef(project);
        setTargetDump(project);
        setTargetReport(project);

        jacocoXmlWriter(xmlPath, file, doc);
    }

    /**
     * xml写入到文件
     *
     * @param xmlPath
     * @param file
     * @param doc
     */
    private void jacocoXmlWriter(String xmlPath, File file, Document doc) throws IOException {
        XMLWriter writer = null;
        try {
            FileWriter fileWriter = new FileWriter(xmlPath);
            OutputFormat format = OutputFormat.createPrettyPrint();
            writer = new XMLWriter(new FileOutputStream(file), format);
            writer.setWriter(fileWriter);
            writer.write(doc);
        } catch (IOException e) {
            throw new IOException(e.getMessage());
        } finally {
            if (writer != null) {
                try {
                    writer.flush();
                } catch (IOException e) {
                    throw new IOException(e.getMessage());
                }
            }
        }
    }

    /**
     * report 设置
     *
     * @param project
     */
    private void setTargetReport(Element project) {
        Element report = project.addElement("target");
        report.addAttribute("name", "report");
        // 删除老数据，创建新数据
        setChildElement(report, "delete", "dir", "${reportfolderPath}");
        setChildElement(report, "mkdir", "dir", "${reportfolderPath}");

        // 报告生成设置根节点
        Element jcReport = report.addElement(new QName("jacoco:report", project.getNamespace()));
        // exec 文件路径设置
        Element exec = jcReport.addElement("executiondata");
        setChildElement(exec, "file", "file", "${jacocoexecPath}");

        //报告生成
        Element structure = setChildElement(jcReport, "structure", "name", "JaCoCo Report");
        Element group = setChildElement(structure, "group", "name", "Check Order related");

        Element classFiles = group.addElement("classfiles");
        //  classfiles ->  fileset
        String[] jacocoGitSourcePath = jacocoConfig.getJacocoGitSourcePath();
        if (Empty.isNotEmpty(jacocoGitSourcePath) && jacocoGitSourcePath.length > 0) {
            for (int i = 0; i < jacocoGitSourcePath.length; i++) {
                String name = i == 0 ? "${checkOrderClasspath}" : "${checkOrderClasspath" + i + "}";
                this.classFileFilest(classFiles, name);
            }
        } else {
            String name = "${checkOrderClasspath}";
            this.classFileFilest(classFiles, name);
        }

        //sourcefiles
        Element sourcefiles = group.addElement("sourcefiles");
        String[] jacocoGitBuildTarget = jacocoConfig.getJacocoGitBuildTarget();
        if (Empty.isNotEmpty(jacocoGitBuildTarget) && jacocoGitBuildTarget.length > 0) {
            for (int i = 0; i < jacocoGitBuildTarget.length; i++) {
                String name = i == 0 ? "${checkOrderSrcpath}" : "${checkOrderSrcpath" + i + "}";
                this.sourceFileFilest(sourcefiles, name);
            }
        } else {
            String name = "${checkOrderSrcpath}";
            this.sourceFileFilest(sourcefiles, name);
        }

//        报告生成地址
        Map<String, String> htmlSet = new HashMap<>();
        htmlSet.put("destdir", "${reportfolderPath}");
        htmlSet.put("encoding", "utf-8");
        setChildElement(jcReport, "html", htmlSet);
    }

    /**
     * 装载 sourcefiles -> fileset
     *
     * @param sourcefiles
     * @param name
     */
    private void sourceFileFilest(Element sourcefiles, String name) {
        Element sourcefiles_fileset = setChildElement(sourcefiles, "fileset", "dir", name);
        if (this.include != null || this.exclude != null) {
            setIncludeOrExcludeFiles(name, sourcefiles_fileset);
        }
    }

    /**
     * 装载classfiles ->  fileset
     *
     * @param classFiles
     * @param name
     */
    private void classFileFilest(Element classFiles, String name) {
        Element classFiles_fileset = setChildElement(classFiles, "fileset", "dir", name);
        if (this.include != null || this.exclude != null) {
            setIncludeOrExcludeFiles(name, classFiles_fileset);
        }
    }

    /**
     * 设置包含或者排除的文件
     *
     * @param fileset
     */
    private void setIncludeOrExcludeFiles(String parentName, Element fileset) {
        if (Empty.isNotEmpty(this.include)) {
            this.include.forEach(cd -> {
                String clazz = cd.substring(cd.lastIndexOf('/') + 1);
                String clazzPage = cd.substring(0, cd.indexOf(clazz)).replace(".", "/");
                String path = clazzPage;
                if (parentName.indexOf("checkOrderClasspath") > -1)
                    path = clazzPage + clazz.replace(".java", ".class");

                if (Empty.isNotEmpty(cd))
                    setChildElement(fileset, "include", "name", path);
            });
        }
        if (Empty.isNotEmpty(this.exclude)) {
            this.exclude.forEach(ex -> {
                if (Empty.isNotEmpty(ex)) {
                    if (parentName.indexOf("checkOrderClasspath") > -1) {
                        ex = ex.replace(".java", ".class");
                    }
                    setChildElement(fileset, "exclude", "name", ex);
                }
            });
        }
    }

    /**
     * dump 任务设置
     *
     * @param project
     */
    private void setTargetDump(Element project) {
        Element dump = project.addElement("target");
        dump.addAttribute("name", "dump");
        Map<String, String> attributes = new HashMap<>();
        attributes.put("address", "${server_ip}");
        attributes.put("reset", "false");
        attributes.put("destfile", "${jacocoexecPath}");
        attributes.put("port", "${server_port}");
        attributes.put("append", "true");
        QName qName = new QName("jacoco:dump", project.getNamespace());
        setChildElement(dump, qName, attributes);
    }

    /**
     * 设置ant 查找jacoco路径
     *
     * @param project
     */
    private void setTaskDef(Element project) {
        Element taskdef = project.addElement("taskdef");
        taskdef.addAttribute("uri", "antlib:org.jacoco.ant");
        taskdef.addAttribute("resource", "org/jacoco/ant/antlib.xml");
        Map<String, String> attribute = new HashMap<>();
        attribute.put("path", "${jacocoantPath}");
        setChildElement(taskdef, "classpath", attribute);
    }

    /**
     * 设置子节点  多个属性
     *
     * @param parent    父节点
     * @param childName 子节点名称
     * @param attribute 子节点属性
     */
    private Element setChildElement(Element parent, String childName, Map<String, String> attribute) {
        Element el = parent.addElement(childName);
        attribute.forEach((k, v) -> {
            el.addAttribute(k, v);
        });
        return el;
    }

    /**
     * 设置子节点  多个属性
     * 带命名空间
     *
     * @param parent    父节点
     * @param childName 子节点名称
     * @param attribute 子节点属性
     */
    private Element setChildElement(Element parent, QName childName, Map<String, String> attribute) {
        Element el = parent.addElement(childName);
        attribute.forEach((k, v) -> {
            el.addAttribute(k, v);
        });
        return el;
    }

    /**
     * 设置子节点  单个属性
     *
     * @param parent
     * @param childName
     * @param key
     * @param value
     * @return
     */
    private Element setChildElement(Element parent, String childName, String key, String value) {
        Element el = parent.addElement(childName);
        el.addAttribute(key, value);
        return el;
    }

    /**
     * 设置根目录
     *
     * @param doc xml对象
     * @return
     */
    private Element setProject(Document doc) {
        Element project = doc.addElement("project");
        project.addNamespace("jacoco", "antlib:org.jacoco.ant");
        project.addAttribute("default", "jacoco");
        return project;
    }

    /**
     * @param project 根目录
     * @param config  property 参数值
     */
    private void setProperties(Element project, Map<String, String> config) {
        config.forEach((k, v) -> {
            Element property = project.addElement("property");
            property.addAttribute("name", k);
            property.addAttribute("value", v);
        });

    }

    private ConfigXml setConfig(Map<String, String> config) {
        this.config = config;
        return this;
    }

    private ConfigXml setInclude(List<String> include) {
        this.include = include;
        return this;
    }

    private ConfigXml setExclude(List<String> exclude) {
        this.exclude = exclude;
        return this;
    }


    class Build {

        public void start() {
//            复制jacocoant.jar 到源文件目录汇总
            copyJacocoAntJar();
            String xmlPath = jacocoConfig.getJacocoPathSource() + "/build.xml";
            System.out.println(xmlPath);
            Map<String, String> config = new HashMap<>();
            config.put("server_ip", jacocoConfig.getJacocoServerIp());
            config.put("server_port", jacocoConfig.getJacocoServerPort());
            if (jacocoConfig.getJacocoGitSourcePath() != null
                    && jacocoConfig.getJacocoGitSourcePath().length > 0) {
                for (int i = 0; i < jacocoConfig.getJacocoGitSourcePath().length; i++) {
                    String path = jacocoConfig.getJacocoPathSource() + File.separator + jacocoConfig.getJacocoGitSourcePath()[i];
                    path = path.replace("//", "/");
                    config.put(i == 0 ? "checkOrderSrcpath" : "checkOrderSrcpath" + i, path);
                }
            } else {
                config.put("checkOrderSrcpath", jacocoConfig.getJacocoPathSource());
            }

            if (jacocoConfig.getJacocoGitBuildTarget() != null
                    && jacocoConfig.getJacocoGitBuildTarget().length > 0) {
                for (int i = 0; i < jacocoConfig.getJacocoGitBuildTarget().length; i++) {
//                    String path = jacocoConfig.getJacocoPathSource() + "/" + jacocoConfig.getJacocoGitBuildTarget()[i];
                    String path = jacocoConfig.getJacocoGitBuildTarget()[i];
                    path = path.replace("//", "/");
                    config.put(i == 0 ? "checkOrderClasspath" : "checkOrderClasspath" + i, path);
                }
            } else {
                config.put("checkOrderClasspath", jacocoConfig.getJacocoPathSource() + "/target/classes");
            }

            config.put("jacocoantPath", jacocoConfig.getJacocoPathSource() + "/jacocoant.jar");
//            config.put("jacocoexecPath", "/Users/chunlonghuang/other_works/jacoco/initmvp_coverage.ec");
            config.put("jacocoexecPath", jacocoConfig.getJacocoPathExec());
            config.put("reportfolderPath", jacocoConfig.getJacocoPathReport());

            List<String> include = null;
            if (Empty.isNotEmpty(jacocoConfig.getClassPath())) {
                include = jacocoConfig.getClassPath();
            }
            if (Empty.isNotEmpty(jacocoConfig.getJacocoGitInclude())) {
                if (Empty.isNotEmpty(include)) include.addAll(jacocoConfig.getJacocoGitInclude());
                else include = jacocoConfig.getJacocoGitInclude();
            }

            List<String> exclude = jacocoConfig.getJacocoGitExculde();

            try {
                setConfig(config).setInclude(include).setExclude(exclude)
                        .jacocoXml(xmlPath);

            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        private void copyJacocoAntJar() {
            String path = jacocoConfig.getJacocoPathSource() + "/jacocoant.jar";
            File file = new File(path);
            if (file.exists()) {
                file.delete();
            }
            InputStream inputStream = null;
            FileOutputStream outputStream = null;
            try {
                file.createNewFile();
                inputStream = this.getClass().getClassLoader().getResourceAsStream("jacocoant.jar");
                outputStream = new FileOutputStream(file);
                byte[] buff = new byte[8192];
                int len = 0;
                while ((len = inputStream.read(buff)) >= 0) {
                    outputStream.write(buff, 0, len);
                }
                outputStream.flush();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    if (inputStream != null) inputStream.close();
                    if (outputStream != null) outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public ConfigXml setConfig(JacocoConfig config) {
        this.jacocoConfig = config;
        return this;
    }

    public void build() {
        new Build().start();
    }
}
